//////////////////////////////////////////////////////////////////////////////
//	Copyright 2011 Alex Leffelman
//	
//	Licensed under the Apache License, Version 2.0 (the "License");
//	you may not use this file except in compliance with the License.
//	You may obtain a copy of the License at
//	
//	http://www.apache.org/licenses/LICENSE-2.0
//	
//	Unless required by applicable law or agreed to in writing, software
//	distributed under the License is distributed on an "AS IS" BASIS,
//	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//	See the License for the specific language governing permissions and
//	limitations under the License.
//////////////////////////////////////////////////////////////////////////////

package com.zhuxulin.midi.event;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;

public class ChannelEvent extends MidiEvent {

	protected int mType;
	protected int mChannel;
	protected int mValue1;
	protected int mValue2;
	public static final int NOTE_OFF = 0x8;
	public static final int NOTE_ON = 0x9;
	public static final int NOTE_AFTERTOUCH = 0xA;
	public static final int CONTROLLER = 0xB;
	public static final int PROGRAM_CHANGE = 0xC;
	public static final int CHANNEL_AFTERTOUCH = 0xD;
	public static final int PITCH_BEND = 0xE;
	private static HashMap<Integer, Integer> mOrderMap;

	protected ChannelEvent(long tick, int type, int channel, int param1,
			int param2) {
		this(tick, 0, type, channel, param1, param2);
	}

	protected ChannelEvent(long tick, long delta, int type, int channel,
			int param1, int param2) {
		super(tick, delta);

		mType = type & 0x0F;
		mChannel = channel & 0x0F;
		mValue1 = param1 & 0xFF;
		mValue2 = param2 & 0xFF;
	}

	public int getType() {
		return mType;
	}

	public void setChannel(int c) {
		if (c < 0) {
			c = 0;
		} else if (c > 15) {
			c = 15;
		}
		mChannel = c;
	}

	public int getChannel() {
		return mChannel;
	}

	protected int getEventSize() {
		switch (mType) {
		case PROGRAM_CHANGE:
		case CHANNEL_AFTERTOUCH:
			return 2;
		default:
			return 3;
		}
	}

	@Override
	public int compareTo(MidiEvent other) {

		if (mTick != other.getTick()) {
			return mTick < other.getTick() ? -1 : 1;
		}
		if (mDelta.getValue() != other.mDelta.getValue()) {
			return mDelta.getValue() < other.mDelta.getValue() ? 1 : -1;
		}

		if (!(other instanceof ChannelEvent)) {
			return 1;
		}

		ChannelEvent o = (ChannelEvent) other;
		if (mType != o.getType()) {

			if (mOrderMap == null) {
				buildOrderMap();
			}

			int order1 = mOrderMap.get(mType);
			int order2 = mOrderMap.get(o.getType());

			return order1 < order2 ? -1 : 1;
		}
		if (mValue1 != o.mValue1) {
			return mValue1 < o.mValue1 ? -1 : 1;
		}
		if (mValue2 != o.mValue2) {
			return mValue2 < o.mValue2 ? -1 : 1;
		}
		if (mChannel != o.getChannel()) {
			return mChannel < o.getChannel() ? -1 : 1;
		}
		return 0;
	}

	@Override
	public boolean requiresStatusByte(MidiEvent prevEvent) {
		if (prevEvent == null) {
			return true;
		}
		if (!(prevEvent instanceof ChannelEvent)) {
			return true;
		}

		ChannelEvent ce = (ChannelEvent) prevEvent;
		return !(mType == ce.getType() && mChannel == ce.getChannel());
	}

	@Override
	public void writeToFile(OutputStream out, boolean writeType)
			throws IOException {
		super.writeToFile(out, writeType);

		if (writeType) {
			int typeChannel = (mType << 4) + mChannel;
			out.write(typeChannel);
		}

		out.write(mValue1);
		if (mType != PROGRAM_CHANGE && mType != CHANNEL_AFTERTOUCH) {
			out.write(mValue2);
		}
	}

	public static ChannelEvent parseChannelEvent(long tick, long delta,
			int type, int channel, InputStream in) throws IOException {

		int val1 = in.read();
		int val2 = 0;
		if (type != PROGRAM_CHANGE && type != CHANNEL_AFTERTOUCH) {
			val2 = in.read();
		}

		switch (type) {
		case NOTE_OFF:
			return new NoteOff(tick, delta, channel, val1, val2);
		case NOTE_ON:
			return new NoteOn(tick, delta, channel, val1, val2);
		case NOTE_AFTERTOUCH:
			return new NoteAftertouch(tick, delta, channel, val1, val2);
		case CONTROLLER:
			return new Controller(tick, delta, channel, val1, val2);
		case PROGRAM_CHANGE:
			return new ProgramChange(tick, delta, channel, val1);
		case CHANNEL_AFTERTOUCH:
			return new ChannelAftertouch(tick, delta, channel, val1);
		case PITCH_BEND:
			return new PitchBend(tick, delta, channel, val1, val2);
		default:
			return new ChannelEvent(tick, delta, type, channel, val1, val2);
		}
	}

	private static void buildOrderMap() {

		mOrderMap = new HashMap<Integer, Integer>();

		mOrderMap.put(PROGRAM_CHANGE, 0);
		mOrderMap.put(CONTROLLER, 1);
		mOrderMap.put(NOTE_ON, 2);
		mOrderMap.put(NOTE_OFF, 3);
		mOrderMap.put(NOTE_AFTERTOUCH, 4);
		mOrderMap.put(CHANNEL_AFTERTOUCH, 5);
		mOrderMap.put(PITCH_BEND, 6);
	}

	@Override
	public byte[] getCmd() {
//		byte[] cmd=null;
		if(this.mType==ChannelEvent.CHANNEL_AFTERTOUCH||this.mType==ChannelEvent.PROGRAM_CHANGE){
			cmd=new byte[2];
		} else {
			// System.out.println("mType:"+mType);
			// System.out.println("mChannel:"+mChannel);
			cmd=new byte[3];
			cmd[2] = (byte) (this.mValue2 & 0xFF);
		}
		cmd[0] = (byte) (((this.mType << 4) | (this.mChannel)) & 0xFF);
		// System.out.println(cmd[0]);
		cmd[1] = (byte) (this.mValue1 & 0xFF);
		return cmd;
	}
}
